package pers.qianyu.module.system.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pers.qianyu.module.core.constants.TokenConstants;
import pers.qianyu.module.core.domain.system.dto.LoginDTO;
import pers.qianyu.module.core.domain.system.entity.SysUserPO;
import pers.qianyu.module.core.domain.system.enums.ClientTypeEnum;
import pers.qianyu.module.core.domain.system.enums.SysUserStatusEnum;
import pers.qianyu.module.core.domain.system.enums.SysUserTypeEnum;
import pers.qianyu.module.core.domain.system.vo.SysUserVO;
import pers.qianyu.module.system.dao.SysUserDao;
import pers.qianyu.module.system.exception.SysErrorCodeEnum;
import pers.qianyu.module.system.exception.SysException;
import pers.qianyu.module.system.service.AuthenticateService;
import pers.qianyu.module.system.service.SysUserService;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * @author mizzle rain
 * @date 2020-10-28 12:51
 */
@Service("authenticateService")
public class AuthenticateServiceImpl implements AuthenticateService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private SysUserDao sysUserDao;
    @Autowired
    private SysUserService sysUserService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String login(LoginDTO loginDTO) {
        String codeFromRedis = (String) redisTemplate.opsForValue()
                .get(TokenConstants.UUID_PREFIX + loginDTO.getUuid());
        String privateKey = (String) redisTemplate.opsForValue()
                .get(TokenConstants.RSA_PUB_PREFIX + loginDTO.getUuid());
        if (!Objects.equals(loginDTO.getCode(), codeFromRedis) || Objects.isNull(privateKey)) {
            throw new SysException(SysErrorCodeEnum.CODE_EXPIRED_OR_ERROR);
        }
        // 用户信息
        QueryWrapper<SysUserPO> wrapper = new QueryWrapper<>();
        wrapper.eq("username", loginDTO.getUsername());
        SysUserPO sysUserPO = sysUserDao.selectOne(wrapper);
        // 账号不存在
        if (Objects.isNull(sysUserPO)) {
            throw new SysException(SysErrorCodeEnum.ACCOUNT_NOT_FOUND);
        }
        // 账号类型不匹配
        if (Objects.nonNull(loginDTO.getClient()) &&
                Objects.equals(loginDTO.getClient(), ClientTypeEnum.MANAGEMENT_SYSTEM.getCode())) {
            // 外层判断：判断是否是从管理系统发来的请求，是则进代码块中
            if (!(Objects.equals(sysUserPO.getType(), SysUserTypeEnum.ADMIN_USER.getCode()) ||
                    Objects.equals(sysUserPO.getType(), SysUserTypeEnum.SYSTEM_USER.getCode()))) {
                throw new SysException(SysErrorCodeEnum.ACCOUNT_NOT_FOUND);
            }
        }
        // 账号锁定
        if (Objects.nonNull(sysUserPO.getStatus()) && Objects.equals(SysUserStatusEnum.DISABLE.getCode(), sysUserPO.getStatus())) {
            throw new SysException(SysErrorCodeEnum.ACCOUNT_IS_LOCKED);
        }
        // 解密 password
        String passwordDecrypted = "";
        try {
            RSA rsa = new RSA(privateKey, null);
            byte[] passwordBytes = Base64.decode(loginDTO.getPassword());
            byte[] decryptBytes = rsa.decrypt(passwordBytes, KeyType.PrivateKey);
            passwordDecrypted = new String(decryptBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new SysException(SysErrorCodeEnum.USERNAME_OR_PASSWORD_ERROR);
        }
        // 校验密码
        if (!BCrypt.checkpw(passwordDecrypted, sysUserPO.getPassword())) {
            throw new SysException(SysErrorCodeEnum.USERNAME_OR_PASSWORD_ERROR);
        }
        // 将验证码移除
        redisTemplate.delete(TokenConstants.UUID_PREFIX + loginDTO.getUuid());
        // 生成 token
        String token = TokenConstants.TOKEN_PREFIX + IdUtil.simpleUUID();
        // 将 VO 对象存入 Redis
        SysUserVO sysUserVO = sysUserService.queryById(sysUserPO.getId());
        // 无操作下 60 分钟token过期
        redisTemplate.opsForValue().set(token, sysUserVO, TokenConstants.TOKEN_EXPIRE_TIME);
        redisTemplate.opsForValue().set(TokenConstants.USER_ID_PREFIX + sysUserVO.getId(),
                token, TokenConstants.TOKEN_EXPIRE_TIME);
        return token;
    }

    @Override
    public SysUserVO getUserInfoByToken(String token) {
        Object value = redisTemplate.opsForValue().get(token);
        if (Objects.isNull(value)) {
            throw new SysException(SysErrorCodeEnum.TOKEN_EXPIRED);
        }
        SysUserVO sysUserVO;
        try {
            sysUserVO = (SysUserVO) value;
        } catch (Exception e) {
            e.printStackTrace();
            throw new SysException(SysErrorCodeEnum.INVALID_TOKEN);
        }
        // 重置 token 的过期时间（续签）
        redisTemplate.opsForValue().set(token, sysUserVO, TokenConstants.TOKEN_EXPIRE_TIME);
        redisTemplate.opsForValue().set(TokenConstants.USER_ID_PREFIX + sysUserVO.getId(),
                token, TokenConstants.TOKEN_EXPIRE_TIME);
        return sysUserVO;
    }

    @Override
    public void delToken(String token) {
        Object value = redisTemplate.opsForValue().get(token);
        if (Objects.nonNull(value)) {
            redisTemplate.delete(token);
        }
    }
}
